/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.security.logging;

import org.apache.lucene.LucenePackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.vacoor.nothing.common.util.IOUtils;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;

public class SystemInfoHandler /*extends RequestHandlerBase*/ {
    private static Logger log = LoggerFactory.getLogger(SystemInfoHandler.class);

    private String hostname = null;
    //  private CoreContainer cc;
    private static final long ONE_KB = 1024L;
    private static final long ONE_MB = 1048576L;
    private static final long ONE_GB = 1073741824L;

    public SystemInfoHandler() {
        init();
    }

    /*
  public SystemInfoHandler(CoreContainer cc) {
    this.cc = cc;
    init();
  }
  */

    private void init() {
        try {
            InetAddress addr = InetAddress.getLocalHost();
            this.hostname = addr.getCanonicalHostName();
        } catch (UnknownHostException e) {
        }
    }


    public static Map<String, Object> getSystemInfo() {
        Map<String, Object> info = new TreeMap<String, Object>();

        OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();
        info.put("name", os.getName());
        info.put("version", os.getVersion());
        info.put("arch", os.getArch());
        info.put("systemLoadAverage", Double.valueOf(os.getSystemLoadAverage()));

        // 尝试获取物理内存
        addGetterIfAvaliable(os, "committedVirtualMemorySize", info);
        addGetterIfAvaliable(os, "freePhysicalMemorySize", info);
        addGetterIfAvaliable(os, "freeSwapSpaceSize", info);
        addGetterIfAvaliable(os, "processCpuTime", info);
        addGetterIfAvaliable(os, "totalPhysicalMemorySize", info);
        addGetterIfAvaliable(os, "totalSwapSpaceSize", info);

        addGetterIfAvaliable(os, "openFileDescriptorCount", info);
        addGetterIfAvaliable(os, "maxFileDescriptorCount", info);
        try {
            if (!os.getName().toLowerCase(Locale.ROOT).startsWith("windows")) {
                info.put("uname", execute("uname -decode"));
                info.put("uptime", execute("uptime"));
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return info;
    }

    static void addGetterIfAvaliable(Object obj, String getter, Map<String, Object> info) {
        try {
            String n = Character.toUpperCase(getter.charAt(0)) + getter.substring(1);
            Method m = obj.getClass().getMethod("get" + n, new Class[0]);
            m.setAccessible(true);
            Object v = m.invoke(obj, (Object[]) null);
            if (v != null) {
                info.put(getter, v);
            }
        } catch (Exception ex) {
            /* ignore */
        }
    }

    private static String execute(String cmd) {
        InputStream in = null;
        Process process = null;
        try {
            process = Runtime.getRuntime().exec(cmd);
            in = process.getInputStream();

            return IOUtils.readText(new InputStreamReader(in, Charset.defaultCharset()), false);
        } catch (Exception ex) {
            return "(error executing: " + cmd + ")";
        } catch (Error err) {
            String str2;
            if ((err.getMessage() != null) && ((err.getMessage().contains("posix_spawn")) || (err.getMessage().contains("UNIXProcess")))) {
                log.warn("Error forking command due to JVM locale bug (see https://issues.apache.org/jira/browse/SOLR-6387): " + err.getMessage());
                return "(error executing: " + cmd + ")";
            }
            throw err;
        } finally {
            if (process != null) {
                IOUtils.close(process.getOutputStream());
                IOUtils.close(process.getInputStream());
                IOUtils.close(process.getErrorStream());
            }
        }
    }

    /**
     * 获取 jvm 信息
     *
     * @return
     */
    public static Map<String, Object> getJvmInfo() {
        Map<String, Object> jvm = new TreeMap<String, Object>();

        String javaVersion = System.getProperty("java.specification.version", "unknown");
        String javaVendor = System.getProperty("java.specification.vendor", "unknown");
        String javaName = System.getProperty("java.specification.name", "unknown");
        String jreVersion = System.getProperty("java.version", "unknown");
        String jreVendor = System.getProperty("java.vendor", "unknown");
        String vmVersion = System.getProperty("java.vm.version", "unknown");
        String vmVendor = System.getProperty("java.vm.vendor", "unknown");
        String vmName = System.getProperty("java.vm.name", "unknown");

        // jvm 概要信息
        jvm.put("name", jreVendor + " " + vmName);
        jvm.put("version", jreVersion + " " + vmVersion);

        // jdk 详细信息
        Map<String, String> java = new TreeMap<String, String>();
        java.put("vendor", javaVendor);
        java.put("name", javaName);
        java.put("version", javaVersion);
        jvm.put("spec", java);

        // jre 详细信息
        Map<String, String> jre = new TreeMap<String, String>();
        jre.put("vendor", jreVendor);
        jre.put("version", jreVersion);
        jvm.put("jre", jre);

        // vm 详细信息
        Map<String, String> vm = new TreeMap<String, String>();
        vm.put("vendor", vmVendor);
        vm.put("name", vmName);
        vm.put("version", vmVersion);
        jvm.put("vm", vm);

        // 处理器核心
        Runtime runtime = Runtime.getRuntime();
        jvm.put("processors", Integer.valueOf(runtime.availableProcessors()));

        // 内存信息
        long free = runtime.freeMemory();
        long max = runtime.maxMemory();
        long total = runtime.totalMemory();
        long used = total - free;
        double percentUsed = used / max * 100.0D;

        // 格式化值
        Map<String, Object> mem = new TreeMap<String, Object>();
        // 原始值
        Map<String, Object> raw = new TreeMap<String, Object>();
        DecimalFormat df = new DecimalFormat("#.#", DecimalFormatSymbols.getInstance(Locale.ROOT));

        raw.put("free", Long.valueOf(free));
        mem.put("free", humanReadableUnits(free, df));
        raw.put("total", Long.valueOf(total));
        mem.put("total", humanReadableUnits(total, df));
        raw.put("max", Long.valueOf(max));
        mem.put("max", humanReadableUnits(max, df));
        raw.put("used", Long.valueOf(used));
        mem.put("used", humanReadableUnits(used, df) + " (%" + df.format(percentUsed) + ")");
        raw.put("used%", Double.valueOf(percentUsed));

        mem.put("raw", raw);
        jvm.put("memory", mem);

        // jmx
        Map<String, Object> jmx = new TreeMap<String, Object>();
        try {
            RuntimeMXBean mx = ManagementFactory.getRuntimeMXBean();
            jmx.put("bootClassPath", mx.getBootClassPath());
            jmx.put("classPath", mx.getClassPath());

            jmx.put("commandLineArgs", mx.getInputArguments());

            jmx.put("startTime", new Date(mx.getStartTime()));
            jmx.put("upTimeMS", Long.valueOf(mx.getUptime()));
        } catch (Exception e) {
            log.warn("Error getting JMX properties", e);
        }
        jvm.put("jmx", jmx);

        return jvm;
    }

    private static Map<String, Object> getLuceneInfo() {
        Map<String, Object> info = new TreeMap<String, Object>();

        Package p; /* = SolrCore.class.getPackage();

        info.put("solr-spec-version", p.getSpecificationVersion());
        info.put("solr-impl-version", p.getImplementationVersion());
        */

        p = LucenePackage.class.getPackage();

        info.put("lucene-spec-version", p.getSpecificationVersion());
        info.put("lucene-impl-version", p.getImplementationVersion());

        return info;
    }

    private static String humanReadableUnits(long bytes, DecimalFormat df) {
        String newSizeAndUnits = "";

        if (bytes / 1073741824L > 0L) {
            newSizeAndUnits = String.valueOf(df.format((float) bytes / 1.073742E+09F)) + " GB";
        } else {
            if (bytes / 1048576L > 0L) {
                newSizeAndUnits = String.valueOf(df.format((float) bytes / 1048576.0F)) + " MB";
            } else {
                if (bytes / 1024L > 0L)
                    newSizeAndUnits = String.valueOf(df.format((float) bytes / 1024.0F)) + " KB";
                else
                    newSizeAndUnits = String.valueOf(bytes) + " bytes";
            }
        }
        return newSizeAndUnits;
    }
}